using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using Microsoft.Practices.EnterpriseLibrary.Validation.Validators;

namespace Microsoft.Practices.EnterpriseLibrary.Validation
{
	/// <summary>
	/// Delegate to a method that returns a value.
	/// </summary>
	public delegate object GetValueHandler();

	/// <summary>
	/// Validates members of an object.
	/// </summary>
	/// <remarks>
	/// Author: Daniel Cazzulino, daniel@cazzulino.com
	/// </remarks>
	public class PropertyValidation
	{
		/// <summary>
		/// Validates that the incoming <paramref name="value"/> is valid according to the 
		/// validation criteria specified for the <paramref name="property"/> on type <typeparamref name="T"/> 
		/// through attributes on type <typeparamref name="T"/> and its ancestors for the default ruleset.
		/// </summary>
		/// <typeparam name="T">The type of object to validate.</typeparam>
		/// <param name="target">The instance of <typeparamref name="T"/> that contains the <paramref name="memberName"/> 
		/// member to validate for the given <paramref name="value"/>.</param>
		/// <param name="property">Property containing the validation criteria.</param>
		/// <param name="value">Value to validate for the given property.</param>
		/// <returns>A collection of with the results of the individual validations.</returns>
		public static ValidationResults Validate<T>(T target, PropertyInfo property, object value)
		{
			return Validate<T>(target, property, value, "");
		}

		/// <summary>
		/// Validates that the incoming <paramref name="value"/> is valid according to the 
		/// validation criteria specified for the <paramref name="property"/> on type <typeparamref name="T"/> 
		/// through attributes on type <typeparamref name="T"/> and its ancestors for the given ruleset.
		/// </summary>
		/// <typeparam name="T">The type of object to validate.</typeparam>
		/// <param name="target">The instance of <typeparamref name="T"/> that contains the <paramref name="memberName"/> 
		/// member to validate for the given <paramref name="value"/>.</param>
		/// <param name="property">Property containing the validation criteria.</param>
		/// <param name="value">Value to validate for the given property.</param>
		/// <param name="ruleset">The rule set to validate.</param>
		/// <returns>A collection of with the results of the individual validations.</returns>
		public static ValidationResults Validate<T>(T target, PropertyInfo property, object value, string ruleset)
		{
			if (target == null) throw new ArgumentNullException("target");
			if (property == null) throw new ArgumentNullException("property");
			if (ruleset == null) throw new ArgumentNullException("ruleset");

			if (property != null)
			{
				Validator validator = GetValidator<T>(property, delegate { return value; }, ruleset);

				return validator.Validate(target);
			}
			else
			{
				return new ValidationResults();
			}
		}

		/// <summary>
		/// Gets a validator for the <paramref name="property"/> of the given 
		/// <paramref name="owner"/> type, for the default ruleset. 
		/// </summary>
		/// <param name="owner">Type that owns the <paramref name="property"/>.</param>
		/// <param name="property">The property that contains the validation criteria attributes.</param>
		/// <param name="valueGetter">Handler to retrieve the incoming value to validate against the property validation criteria, 
		/// for a given target object of type <typeparamref name="T"/>.</param>
		public static Validator GetValidator(Type owner, PropertyInfo property, GetValueHandler valueGetter)
		{
			return GetValidator(owner, property, valueGetter, "");
		}

		/// <summary>
		/// Gets a validator for the <paramref name="property"/> of the given 
		/// <typeparamref name="T"/> type, for the incoming <paramref name="value"/> and 
		/// default ruleset.
		/// </summary>
		/// <typeparam name="T">Type that owns the <paramref name="property"/>.</typeparam>
		/// <param name="property">The property that contains the validation criteria attributes.</param>
		/// <param name="valueGetter">Handler to retrieve the incoming value to validate against the property validation criteria, 
		/// for a given target object of type <typeparamref name="T"/>.</param>
		public static Validator GetValidator<T>(PropertyInfo property, GetValueHandler valueGetter)
		{
			return GetValidator(typeof(T), property, valueGetter, "");
		}

		/// <summary>
		/// Gets a validator for the <paramref name="property"/> of the given 
		/// <typeparamref name="T"/> type, for the incoming <paramref name="value"/> and 
		/// the <paramref name="ruleset"/> specified.
		/// </summary>
		/// <typeparam name="T">Type that owns the <paramref name="property"/>.</typeparam>
		/// <param name="property">The property that contains the validation criteria attributes.</param>
		/// <param name="valueGetter">Handler to retrieve the incoming value to validate against the property validation criteria, 
		/// for a given target object of type <typeparamref name="T"/>.</param>
		/// <param name="ruleset">The rule set to get the validator for.</param>
		public static Validator GetValidator<T>(PropertyInfo property, GetValueHandler valueGetter, string ruleset)
		{
			return GetValidator(typeof(T), property, valueGetter, ruleset);
		}

		/// <summary>
		/// Gets a validator for the <paramref name="property"/> of the given 
		/// <paramref name="owner"/> type, for the given the <paramref name="ruleset"/>.
		/// </summary>
		/// <param name="owner">Type that owns the <paramref name="property"/>.</param>
		/// <param name="property">The property that contains the validation criteria attributes.</param>
		/// <param name="valueGetter">Handler to retrieve the incoming value to validate against the property validation criteria, 
		/// for a given target object of type <typeparamref name="T"/>.</param>
		/// <param name="ruleset">The rule set to get the validator for.</param>
		public static Validator GetValidator(Type owner, PropertyInfo property, GetValueHandler valueGetter, string ruleset)
		{
			if (owner == null) throw new ArgumentNullException("owner");
			if (property == null) throw new ArgumentNullException("property");
			if (ruleset == null) throw new ArgumentNullException("ruleset");
			if (valueGetter == null) throw new ArgumentNullException("valueGetter");

			return PropertyValidationFactory.GetPropertyValidator(owner, property, ruleset,
				new MemberAccess(property.Name, valueGetter));
		}

		class MemberAccess : MemberValueAccessBuilder
		{
			string propertyName;
			GetValueHandler valueGetter;

			public MemberAccess(string propertyName, GetValueHandler value)
			{
				this.propertyName = propertyName;
				this.valueGetter = value;
			}

			protected override ValueAccess DoGetFieldValueAccess(FieldInfo fieldInfo)
			{
				throw new NotSupportedException();
			}

			protected override ValueAccess DoGetMethodValueAccess(MethodInfo methodInfo)
			{
				throw new NotSupportedException();
			}

			protected override ValueAccess DoGetPropertyValueAccess(PropertyInfo propertyInfo)
			{
				if (propertyInfo.Name == propertyName)
					return new ValueGetterAccess(propertyName, valueGetter);
				else
					return new PropertyValueAccess(propertyInfo);
			}

			class ValueGetterAccess : ValueAccess
			{
				string propertyName;
				GetValueHandler valueGetter;

				public ValueGetterAccess(string propertyName, GetValueHandler valueGetter)
				{
					this.propertyName = propertyName;
					this.valueGetter = valueGetter;
				}

				public override bool GetValue(object source, out object value, out string valueAccessFailureMessage)
				{
					value = valueGetter();
					valueAccessFailureMessage = null;
					return true;
				}

				public override string Key
				{
					get { return propertyName; }
				}
			}
		}
	}
}
